using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Sandia.MVCFramework.Common
{
    /// <summary>
    /// Delegate used for events related to thread creation within a 
    /// ThreadSequenceTracker.
    /// </summary>
    /// <param name="sequenceTracker">
    /// The ThreadSequenceTracker in which the thread was created.
    /// </param>
    /// <param name="thread">The new thread.</param>
    public delegate void OnThreadCreated(ThreadSequenceTracker sequenceTracker, Thread thread);

    /// <summary>
    /// Delegate used for events related to thread completion withing a 
    /// ThreadSequenceTracker.
    /// </summary>
    /// <param name="sequenceTracker">
    /// The ThreadSequenceTracker in which the thread completed.
    /// </param>
    /// <param name="thread">The thread that is completed.</param>
    public delegate void OnThreadExecutionComplete(ThreadSequenceTracker sequenceTracker, Thread thread);

    /// <summary>
    /// Delegate for events related to the completion of a thread sequence in
    /// a ThreadSequenceTracker.
    /// </summary>
    /// <param name="sequenceTracker">The sequence tracker.</param>
    public delegate void OnSequenceComplete(ThreadSequenceTracker sequenceTracker);

    /// <summary>
    /// Delegate for functions that are executed on a new thread within a 
    /// ThreadSequenceTracker.
    /// </summary>
    /// <param name="obj">A single parameter to the function.</param>
    public delegate void ThreadFunction(object obj);

    /// <summary>
    /// A class used to track sequences of threads.
    /// </summary>
    public class ThreadSequenceTracker
    {
        /// <summary>
        /// The number of currently active threads.
        /// </summary>
        private int threadCount = 0;

        /// <summary>
        /// Called when a thread is created.
        /// </summary>
        public event OnThreadCreated ThreadCreated;

        /// <summary>
        /// Called when a thread completes execution.
        /// </summary>
        public event OnThreadExecutionComplete ThreadExecutionComplete;

        /// <summary>
        /// Called when the sequence is complete. This event fires when there
        /// are zero active threads left.
        /// </summary>
        public event OnSequenceComplete SequenceComplete;

        /// <summary>
        /// Executes the given function in a new thread in the sequence.
        /// </summary>
        /// <param name="target">The function to execute.</param>
        /// <param name="parameter">
        /// The parameter to pass to the function.
        /// </param>
        public void AddThread(
            ThreadFunction target,
            object parameter)
        {
            threadCount++;
            ThreadPool.QueueUserWorkItem(
                new WaitCallback(this.execute), 
                new ThreadFunctionInfo(target, parameter));
        }

        /// <summary>
        /// Executes a ThreadFunction and fires events related to the progess of
        /// the execution.
        /// </summary>
        /// <param name="parameter">
        /// A ThreadFunctionInfo containing the function to execute.
        /// </param>
        private void execute(
            object parameter)
        {
            Thread currentThread = Thread.CurrentThread;

            this.fireThreadCreated(currentThread);

            ThreadFunctionInfo functionInfo = (ThreadFunctionInfo)parameter;

            functionInfo.Function(functionInfo.State);

            // Now fire the event so say that execution is complete for this thread.
            this.fireExecutionComplete(currentThread);
        }

        /// <summary>
        /// Fires the ThreadCreated event.
        /// </summary>
        /// <param name="thread">The new thread.</param>
        private void fireThreadCreated(
            Thread thread)
        {
            if (this.ThreadCreated != null)
            {
                this.ThreadCreated(this, thread);
            }
        }

        /// <summary>
        /// Fires the ThreadExecutionComplete event.
        /// </summary>
        /// <param name="thread">The thread that completed.</param>
        private void fireExecutionComplete(
            Thread thread)
        {
            this.threadCount--;
            if (this.ThreadExecutionComplete != null)
            {
                this.ThreadExecutionComplete(this, thread);
            }

            if (this.threadCount == 0 && this.SequenceComplete != null)
            {
                this.SequenceComplete(this);
            }
        }

        /// <summary>
        /// A class used to carry a function to be executed in the 
        /// ThreadSequenceTracker.
        /// </summary>
        private class ThreadFunctionInfo
        {
            /// <summary>
            /// The function.
            /// </summary>
            private ThreadFunction function;

            /// <summary>
            /// The function.
            /// </summary>
            public ThreadFunction Function
            {
                get { return this.function; }
            }

            /// <summary>
            /// The parameter to pass to the function.
            /// </summary>
            private object state;

            /// <summary>
            /// The parameter to pass to the function.
            /// </summary>
            public object State
            {
                get { return this.state; }
            }

            /// <summary>
            /// Constructor for ThreadFunctionInfo.
            /// </summary>
            /// <param name="function">The function.</param>
            /// <param name="state">
            /// The parameter to pass to the function.
            /// </param>
            public ThreadFunctionInfo(
                ThreadFunction function,
                object state)
            {
                this.function = function;
                this.state = state;
            }
        }
    }
}
